iT邦幫忙

2026 iThome 鐵人賽

DAY 2
0
ChatGPT & Codex

窮鬼冰箱升級計劃系列 第 2

【Day 2】初探物聯網晶片 ESP32 及開發環境設定

  • 分享至 

  • xImage
  •  

前言

昨天準備了硬體,今天就接著繼續緩慢的開發前進吧。
買回板子或是模組之後,建議先不要急著連接其他裝置。有一定的機率買到有瑕疵的設備,根據先前在祥昌購物的經驗,可以憑發票送檢,幾天後就會更換一塊。

準備

連接硬體與測試

因為 ESP32 有諸多版本,而手邊這塊 ESP32-S3-CAM GOOUUU 的各個 腳位,從 Arduino 網站上面查看其定義。 ( 連結:https://forum.arduino.cc/t/chinese-esp32-s3-cam/1318290 )
image

https://europe1.discourse-cdn.com/arduino/original/4X/c/b/0/cb0022a019eb5e3284d670bf1c5b86cac39b4446.jpeg

所以,剛剛提到的「測試板子」的做法,就是拿一條線,連接電腦和板子的 TTL (圖中右下的那個 USB Type-C)。

安裝 Arduino IDE

  • 首次安裝,可以參考文件: https://support.arduino.cc/hc/en-us/articles/360019833020-Download-and-install-Arduino-IDE
  • 裝好之後,打開 Arduino IDE,從上面 「Select Board」的下拉選單直接選取
    https://ithelp.ithome.com.tw/upload/images/20260917/20130149Q9hcN5Suk4.png
  • 或者從 Others 查看 USB 是否有抓到 ESP32-S3
    https://ithelp.ithome.com.tw/upload/images/20260917/201301490rHXRr36lc.png
  • 選擇好了之後,就測測看燒錄功能是否正常⋯⋯ 將下面這串貼到 IDE 中,儲存後,按左上角 Upload 吧
    void setup() {
      Serial.begin(115200);
      Serial.println("Hello ESP32-S3");
    }
    
    void loop() {
      delay(1000);
    }
    
  • 當然也是可以跳過這部,直接到下一段

古早開發流

  • 因為今天預計要把紅外線模組,連到 ESP32 後,偵測人類活動,所以確認兩造硬體型號
    • 板子: ESP32-S3-CAM GOOUUU
    • 紅外線模組: SR602
  • 以往,在把玩這些板子和模組,總要花不少時間去看有沒有熱心的人,用了類似的模組,做了些什麼,藉此快速理解硬體的玩法,還有別人踩過什麼坑。 如果不巧剛好都沒有,就會去看 GitHub 是否有些關於這個模組的 Example Code

透過 Prompt 開發

  • 這次使用 Prompt 讓 ChatGPT 吐答案
現在準備要初始化 ESP32-S3-CAM GOOUUU,連接紅外線感測器 SR602,偵測有人活動連續三秒,就拍下一張照片,存到 SD 卡。
  • 交叉使用古早方法...這好像什麼交叉詰問,YES or NO

  • ChatGPT 回覆
    https://ithelp.ithome.com.tw/upload/images/20260917/201301491WL6NsbAkO.png

  • 直接產出原始碼

    #include "esp_camera.h"
    #include "FS.h"
    #include "SD_MMC.h"
    
    // ======================================================
    // GOOUUU ESP32-S3-CAM V1.5
    // OV3660 + SR602 PIR + microSD
    // ======================================================
    
    // ---------- SR602 ----------
    #define PIR_PIN 2
    
    // 必須連續偵測 3 秒
    const unsigned long MOTION_TRIGGER_MS = 3000;
    
    // ---------- Camera ----------
    #define PWDN_GPIO_NUM     -1
    #define RESET_GPIO_NUM    -1
    
    #define XCLK_GPIO_NUM     15
    #define SIOD_GPIO_NUM      4
    #define SIOC_GPIO_NUM      5
    
    #define Y9_GPIO_NUM       16
    #define Y8_GPIO_NUM       17
    #define Y7_GPIO_NUM       18
    #define Y6_GPIO_NUM       12
    #define Y5_GPIO_NUM       10
    #define Y4_GPIO_NUM        8
    #define Y3_GPIO_NUM        9
    #define Y2_GPIO_NUM       11
    
    #define VSYNC_GPIO_NUM     6
    #define HREF_GPIO_NUM      7
    #define PCLK_GPIO_NUM     13
    
    // ---------- microSD ----------
    #define SD_CLK  39
    #define SD_CMD  40
    #define SD_D0   38
    
    
    unsigned long motionStart = 0;
    
    bool timingMotion = false;
    bool photoTaken = false;
    
    uint32_t photoNumber = 0;
    
    
    // ======================================================
    // Camera Initialization
    // ======================================================
    
    bool initCamera() {
    
      camera_config_t config = {};
    
      config.ledc_channel = LEDC_CHANNEL_0;
      config.ledc_timer   = LEDC_TIMER_0;
    
      config.pin_d0 = Y2_GPIO_NUM;
      config.pin_d1 = Y3_GPIO_NUM;
      config.pin_d2 = Y4_GPIO_NUM;
      config.pin_d3 = Y5_GPIO_NUM;
      config.pin_d4 = Y6_GPIO_NUM;
      config.pin_d5 = Y7_GPIO_NUM;
      config.pin_d6 = Y8_GPIO_NUM;
      config.pin_d7 = Y9_GPIO_NUM;
    
      config.pin_xclk  = XCLK_GPIO_NUM;
      config.pin_pclk  = PCLK_GPIO_NUM;
      config.pin_vsync = VSYNC_GPIO_NUM;
      config.pin_href  = HREF_GPIO_NUM;
    
      config.pin_sccb_sda = SIOD_GPIO_NUM;
      config.pin_sccb_scl = SIOC_GPIO_NUM;
    
      config.pin_pwdn  = PWDN_GPIO_NUM;
      config.pin_reset = RESET_GPIO_NUM;
    
      config.xclk_freq_hz = 20000000;
    
      // 直接讓 Camera 輸出 JPEG
      config.pixel_format = PIXFORMAT_JPEG;
    
      // 第一階段先 VGA,確認整套流程穩定
      config.frame_size = FRAMESIZE_VGA;
    
      config.jpeg_quality = 10;
    
      if (psramFound()) {
    
        Serial.println("PSRAM detected");
    
        config.fb_location = CAMERA_FB_IN_PSRAM;
        config.fb_count = 2;
    
        config.grab_mode = CAMERA_GRAB_LATEST;
    
      } else {
    
        Serial.println("WARNING: PSRAM not detected");
    
        config.fb_location = CAMERA_FB_IN_DRAM;
        config.fb_count = 1;
      }
    
    
      esp_err_t err = esp_camera_init(&config);
    
      if (err != ESP_OK) {
    
        Serial.printf(
          "Camera init failed: 0x%x\n",
          err
        );
    
        return false;
      }
    
    
      sensor_t *sensor = esp_camera_sensor_get();
    
      if (sensor != nullptr) {
    
        Serial.printf(
          "Camera sensor PID: 0x%04X\n",
          sensor->id.PID
        );
      }
    
    
      Serial.println("Camera OK");
    
      return true;
    }
    
    
    // ======================================================
    // SD Initialization
    // ======================================================
    
    bool initSD() {
    
      // GOOUUU V1.5 SD pins
      SD_MMC.setPins(
        SD_CLK,
        SD_CMD,
        SD_D0
      );
    
    
      // true = 1-bit SDMMC mode
      if (!SD_MMC.begin(
            "/sdcard",
            true
          )) {
    
        Serial.println(
          "SD initialization failed"
        );
    
        return false;
      }
    
    
      if (SD_MMC.cardType() == CARD_NONE) {
    
        Serial.println(
          "No SD card detected"
        );
    
        return false;
      }
    
    
      uint64_t sizeMB =
        SD_MMC.cardSize() / (1024 * 1024);
    
    
      Serial.printf(
        "SD Card: %llu MB\n",
        sizeMB
      );
    
    
      if (!SD_MMC.exists("/photos")) {
    
        SD_MMC.mkdir("/photos");
      }
    
    
      Serial.println("SD OK");
    
      return true;
    }
    
    
    // ======================================================
    // Find next photo number
    // 避免 ESP32 重開機後從 000001 開始覆寫
    // ======================================================
    
    void findNextPhotoNumber() {
    
      File dir = SD_MMC.open("/photos");
    
      if (!dir) {
        return;
      }
    
    
      uint32_t maxNumber = 0;
    
    
      while (true) {
    
        File file = dir.openNextFile();
    
        if (!file) {
          break;
        }
    
    
        String name = file.name();
    
    
        int start =
          name.lastIndexOf("photo_");
    
    
        if (start >= 0) {
    
          String number =
            name.substring(
              start + 6,
              start + 12
            );
    
    
          uint32_t n =
            number.toInt();
    
    
          if (n > maxNumber) {
            maxNumber = n;
          }
        }
    
    
        file.close();
      }
    
    
      dir.close();
    
      photoNumber = maxNumber;
    
    
      Serial.printf(
        "Last photo: %06lu\n",
        (unsigned long)photoNumber
      );
    }
    
    
    // ======================================================
    // Capture
    // ======================================================
    
    bool takePhoto() {
    
      Serial.println();
      Serial.println("------------------------");
      Serial.println("Taking photo...");
    
    
      camera_fb_t *fb =
        esp_camera_fb_get();
    
    
      if (!fb) {
    
        Serial.println(
          "Capture failed"
        );
    
        return false;
      }
    
    
      photoNumber++;
    
    
      char path[40];
    
      snprintf(
        path,
        sizeof(path),
        "/photos/photo_%06lu.jpg",
        (unsigned long)photoNumber
      );
    
    
      Serial.printf(
        "Resolution: %d x %d\n",
        fb->width,
        fb->height
      );
    
    
      Serial.printf(
        "JPEG: %u bytes\n",
        fb->len
      );
    
    
      File file =
        SD_MMC.open(
          path,
          FILE_WRITE
        );
    
    
      if (!file) {
    
        Serial.println(
          "Cannot create photo"
        );
    
        esp_camera_fb_return(fb);
    
        return false;
      }
    
    
      size_t written =
        file.write(
          fb->buf,
          fb->len
        );
    
    
      file.close();
    
      esp_camera_fb_return(fb);
    
    
      if (written == 0) {
    
        Serial.println(
          "SD write failed"
        );
    
        return false;
      }
    
    
      Serial.printf(
        "Saved: %s\n",
        path
      );
    
      Serial.printf(
        "Written: %u bytes\n",
        written
      );
    
      Serial.println("------------------------");
    
      return true;
    }
    
    
    // ======================================================
    // Setup
    // ======================================================
    
    void setup() {
    
      Serial.begin(115200);
    
      delay(2000);
    
    
      Serial.println();
      Serial.println("==============================");
      Serial.println(" Smart Fridge Camera");
      Serial.println(" GOOUUU ESP32-S3-CAM V1.5");
      Serial.println(" OV3660 + SR602");
      Serial.println("==============================");
    
    
      pinMode(
        PIR_PIN,
        INPUT
      );
    
    
      Serial.println();
      Serial.println("[1] Camera");
    
      if (!initCamera()) {
    
        Serial.println(
          "FATAL: Camera failed"
        );
    
        while (true) {
          delay(1000);
        }
      }
    
    
      Serial.println();
      Serial.println("[2] SD Card");
    
      if (!initSD()) {
    
        Serial.println(
          "FATAL: SD failed"
        );
    
        while (true) {
          delay(1000);
        }
      }
    
    
      findNextPhotoNumber();
    
    
      Serial.println();
      Serial.println("==============================");
      Serial.println("SYSTEM READY");
      Serial.println("Waiting for motion...");
      Serial.println("==============================");
    }
    
    
    // ======================================================
    // Loop
    // ======================================================
    
    void loop() {
    
      bool motion =
        digitalRead(PIR_PIN);
    
    
      // ==============================================
      // SR602 HIGH
      // ==============================================
    
      if (motion == HIGH) {
    
        // 第一次 HIGH
        if (!timingMotion) {
    
          timingMotion = true;
    
          motionStart = millis();
    
          Serial.println(
            "Motion detected..."
          );
        }
    
    
        // HIGH 必須連續 3 秒
        if (
          !photoTaken &&
          millis() - motionStart >= MOTION_TRIGGER_MS
        ) {
    
          Serial.println(
            "Motion sustained for 3 seconds"
          );
    
    
          if (takePhoto()) {
    
            photoTaken = true;
    
          } else {
    
            Serial.println(
              "Photo failed"
            );
          }
        }
    
      }
    
      // ==============================================
      // SR602 LOW
      // ==============================================
    
      else {
    
        // HIGH 不足三秒
        if (
          timingMotion &&
          !photoTaken
        ) {
    
          Serial.println(
            "Motion ended before 3 seconds"
          );
        }
    
    
        // 已經拍照,等人離開才重新 Arm
        if (
          timingMotion &&
          photoTaken
        ) {
    
          Serial.println(
            "Motion ended - system rearmed"
          );
        }
    
    
        timingMotion = false;
        photoTaken = false;
    
        motionStart = 0;
      }
    
    
      delay(20);
    }
    

本日進度 - ESP32-S3 初始化

  1. 安裝 Arduino IDE
  2. 安裝 ESP32 開發板支援包
  3. 透過 Prompt 產出原始碼
  4. 燒入程式
  5. (下回繼續) 透過 Serial Monitor 查看除錯訊息
  6. (下回繼續) 檢查程式邏輯

上一篇
【Day 1】專案起源、定義冰箱管理方式
下一篇
【Day 3】ESP32 上的連網功能
系列文
窮鬼冰箱升級計劃3
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言